package self.zhangkang.chart.service.index

import self.zhangkang.chart.data.KLineInfo
import self.zhangkang.chart.data.WR

/**
 * @author : Android-张康
 * created on: 2023/5/9 11:11
 * description: 计算股票的 WR
 *
 * 威廉指标WR计算公式
 * 威廉指标的计算主要是利用分析周期内的最高价、最低价及周期结束的收盘价等三者之间的关系展开的。以日威廉指标为例，其计算公式为：
 * WR＝（Hn－C）÷（Hn－Ln）×100
 * 其中：C为计算日的收盘价，Ln为N周期内的最低价，Hn为N周期内的最高价，公式中的N为选定的计算时间参数，一般为4或14
 *
 * @param n 周期
 * @param getHighPrice 获取最高价
 * @param getLowPrice 获取最低价
 * @param getClosePrice 获取收盘价
 */
class WRTools<T>(
    private val n: Int,
    private val getHighPrice: (T) -> Float,
    private val getLowPrice: (T) -> Float,
    private val getClosePrice: (T) -> Float
) {

    companion object {
        /**
         * WR1参数
         */
        private const val WR1 = 10

        /**
         * WR2参数
         */
        private const val WR2 = 6

        /**
         * 计算 WR 值
         */
        fun calculationWR(dataList: MutableList<KLineInfo>) {
            val getHighPrice: (KLineInfo) -> Float = {
                it.highPrice
            }
            val getLowPrice: (KLineInfo) -> Float = {
                it.lowerPrice
            }
            val getClosePrice: (KLineInfo) -> Float = {
                it.closePrice
            }

            val wr1Tools = WRTools(
                n = WR1,
                getHighPrice = getHighPrice,
                getLowPrice = getLowPrice,
                getClosePrice = getClosePrice
            )
            val wr2Tools = WRTools(
                n = WR2,
                getHighPrice = getHighPrice,
                getLowPrice = getLowPrice,
                getClosePrice = getClosePrice
            )
            dataList.forEachIndexed { index, kLineInfo ->
                val wr1 = wr1Tools.calculationWR(
                    data = dataList,
                    currentIndex = index,
                )
                val wr2 = wr2Tools.calculationWR(
                    data = dataList,
                    currentIndex = index
                )
                dataList[index] = kLineInfo.copy(wr = WR(wr1 = wr1, wr2 = wr2))
            }
        }
    }

    private val mCache = FloatArray(2)

    /**
     * 计算 WR
     *
     * @param data 当前的数据
     * @param currentIndex 当前数据
     */
    private fun calculationWR(
        data: List<T>,
        currentIndex: Int,
    ): Float {
        /*
        * 威廉指标WR计算公式
        * 威廉指标的计算主要是利用分析周期内的最高价、最低价及周期结束的收盘价等三者之间的关系展开的。以日威廉指标为例，其计算公式为：
        * WR＝（Hn－C）÷（Hn－Ln）×100
        * 其中：C为计算日的收盘价，Ln为N周期内的最低价，Hn为N周期内的最高价，公式中的N为选定的计算时间参数，一般为4或14
        */
        val highAndLow = getHighAndLow(
            currentIndex = currentIndex,
            dataList = data,
        )
        val Hn = highAndLow[0]
        val Ln = highAndLow[1]
        val C = getClosePrice(data[currentIndex])
        val HnC = Hn - C
        val HnLn = Hn - Ln
        if (HnC == 0F || HnLn == 0F) {
            return 0F
        }
        var wr = HnC / HnLn * 100
        wr = maxOf(0F, wr)
        wr = minOf(100F, wr)
        return wr
    }

    /**
     * 得到某区间内最高价和最低价
     *
     * @param currentIndex 当前位置
     * @param dataList 数据列表
     * @return [FloatArray] 0 周期内的最高价，1 周期内的最低价
     */
    private fun getHighAndLow(
        currentIndex: Int,
        dataList: List<T>,
    ): FloatArray {
        var startIndex = currentIndex - n + 1
        if (startIndex < 0) {
            startIndex = 0
        }
        var high = Float.MIN_VALUE
        var low = Float.MAX_VALUE
        for (i in startIndex..currentIndex) {
            val data = dataList[i]
            high = maxOf(high, getHighPrice(data))
            low = minOf(low, getLowPrice(data))
        }
        mCache[0] = high
        mCache[1] = low
        return mCache
    }
}